#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include "crypto/types.h"
#include "crypto/errors.h"
#include "crypto/json.h"
#include "crypto/base64.h"
#include "crypto/des.h"
#include "crypto/md5.h"
#include "crypto/rsa.h"
#include "tls/client.h"
#include "tls/common.h"

User *user;
// getsockname(connfd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);/
static pthread_mutex_t userMutex = PTHREAD_MUTEX_INITIALIZER;

int TcpClient(char *ip, uint16 port)
{
    int fd;
    int ret;
    char buffer[2048], buffer2[2048];
    struct sockaddr_in *serverAddr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in)); // 对端，即目标地址信息
    uint32 serverAddrIp;
    fd = socket(AF_INET, SOCK_STREAM, 0);
    assert(fd >= 0);
    int64 e, n;
    serverAddr->sin_family = AF_INET;
    serverAddr->sin_port = htons(port);
    inet_pton(AF_INET, ip, &serverAddrIp);
    serverAddr->sin_addr.s_addr = serverAddrIp;
    ret = connect(fd, (struct sockaddr *)serverAddr, sizeof(struct sockaddr));
    if (ret < 0)
    {
        perror("connect");
        sprintf(buffer, "[client] socket connect failed.");
        ERROR(buffer);
        return -1;
    }

    sprintf(buffer, "[client] Connect to %s:%d success.", ip, port);
    INFO(buffer);
    char clientRandom[8], serverRandom[8], premasterKey[8];
    Json *json = JsonInit();
    JsonItem *jsonItem;
    JsonValue *jsonValue;
    char *strPtr;
    int len;
    SAFE_array *originalData = (SAFE_array *)malloc(sizeof(SAFE_array) + 100);
    SAFE_array *decodeData = (SAFE_array *)malloc(sizeof(SAFE_array) + 100);
    /*******************************************************************************
     * 1. Client hello {type:"client hello",clientRandom:"base64 encode clientRandom data!"}
     ******************************************************************************/
    JsonPut(json, JsonStringItemInit("type", "client hello"));
    generateOriginalCryptoKey(clientRandom);
    jsonValue = JsonValueInit(JSON_VALUE_TYPE_STRING);
    jsonValue->value = malloc(sizeof(20));
    memcpy(originalData->data, clientRandom, 8);
    originalData->size = 9;
    ((char *)originalData->data)[8] = 0;
    originalData->cap = 100;
    decodeData->cap = 100;
    decodeData->size = 0;
    encodeBase64(originalData, &decodeData);
    memcpy(jsonValue->value, decodeData->data, decodeData->size);
    ((char *)(jsonValue->value))[decodeData->size] = 0;
    printf("Send base64 decode clientRandom: %s\n", (char *)(jsonValue->value));
    jsonItem = JsonItemInit("clientRandom", jsonValue);
    JsonPut(json, jsonItem);
    JsonString(json, buffer);
    ret = write(fd, buffer, strlen(buffer));
    if (ret < 0)
    {
        perror("write");
        exit(0);
    }
    printf("Connnecting step 1: Client hello finish. \n");
    /*******************************************************************************
     * 2. Server hello {type:"server hello",serverRandom:"base64 encode serverRandom data!"}
     ******************************************************************************/
    len = read(fd, buffer, 2047);
    buffer[len] = 0;
    JsonFree(json);
    json = ParseJson(buffer);
    assert(json);
    jsonItem = JsonGet(json, "serverRandom");
    strPtr = (char *)jsonItem->value->value;
    len = strlen(strPtr);

    printf("Receive base64 decode serverRandom: %s\n", strPtr);
    memcpy(decodeData->data, strPtr, len);
    decodeData->size = len;
    decodeBase64(decodeData, &originalData);
    memcpy(serverRandom, originalData->data, 8);

    printf("Connnecting step 2: Server hello finish. \n");
    /*******************************************************************************
     * 3. Server key exchange (RSA) {type: "server key exchange", e: 257, n: 32位大整数}
     ******************************************************************************/
    len = read(fd, buffer, 2047);
    buffer[len] = 0;
    JsonFree(json);
    json = ParseJson(buffer);
    assert(json);
    e = 257;
    jsonItem = JsonGet(json, "n");
    assert(jsonItem);
    n = *(int64 *)(jsonItem->value->value);

    printf("Connnecting step 3: Server key exchange finish. \n");
    /****************************************************************
     * 4. Server hello done {type: "server hello done"}
     *****************************************************************/
    len = read(fd, buffer, 2047);
    buffer[len] = 0;
    JsonFree(json);
    json = ParseJson(buffer);
    assert(json);
    jsonItem = JsonGet(json, "type");
    assert(jsonItem);
    strPtr = (char *)jsonItem->value->value;
    assert(!strcmp(strPtr, "server hello done"));

    printf("Connnecting step 4: Server hello done finish. \n");
    /****************************************************************
     * 5. client key exchange (RSA) {type: "client key exchange", preMasterSecret: "rsa encode key"}
     * preMasterSecret (8bit + 1bit 0) -> rsa (36位) -> base64(48位)
     *****************************************************************/
    JsonPut(json, JsonStringItemInit("type", "client key exchange"));
    generateOriginalCryptoKey(premasterKey);
    // printf("preMasterKey: %llu\n", *(uint64 *)premasterKey);
    sprintf(buffer, "[System] The clientRandom is %llu. "
                    "The serverRandom is %llu. The premaster key is %llu. ",
            *(uint64 *)clientRandom, *(uint64 *)serverRandom, *(uint64 *)premasterKey);
    INFO(buffer);
    ((char *)decodeData->data)[8] = 0;
    memcpy(decodeData->data, premasterKey, 9);
    decodeData->size = 9;
    SAFE_array *rsaEncodeData = RSAEncode(n, e, decodeData);

    rsaEncodeData->data[rsaEncodeData->size] = 0;
    // printf("rsa-PreMasterKey-len: %d\n", rsaEncodeData->size);
    encodeBase64(rsaEncodeData, &originalData);
    originalData->data[originalData->size] = 0;
    // printf("base64PreMasterKey: %d %s\n", originalData->size, (char *)originalData->data);
    free(rsaEncodeData);
    JsonPut(json, JsonStringItemInit("preMasterSecret", originalData->data));
    JsonString(json, buffer);
    len = strlen(buffer);
    write(fd, buffer, len);

    printf("Connnecting step 5: Client key exchange. \n");
    /****************************************************************
     *  6. Server ChangeCipherSpec. No need TCP communication.
     *****************************************************************/
    user = (User *)malloc(sizeof(User));
    strcpy(user->nickName, "Anonymous");
    user->fd = fd;
    user->next = NULL;
    generateFinalDesKey(*((uint64 *)clientRandom), *((uint64 *)serverRandom), *((uint64 *)premasterKey), user->desKey);
    user->clientAddr = serverAddr;
    sprintf(buffer, "[System] The deskey key is %llu", *(uint64 *)user->desKey);
    INFO(buffer);
    printf("%s\n", buffer);
    printf("Connnecting step 6: Client ChangeCipherSpec. \n");
    /****************************************************************
     *  7. send client finish. Use Des + MD5 deal data. {type:"finished"}
     *****************************************************************/
    sleep(1);
    JsonRemove(json, "preMasterSecret");
    JsonPut(json, JsonStringItemInit("type", "finished"));
    JsonString(json, buffer);
    len = strlen(buffer);
    len = TCPSend(buffer, len, user->desKey, buffer2);
    assert(write(user->fd, buffer2, len) > 0);

    printf("Connnecting step 7: Send client finish!\n");
    /****************************************************************
     *  8. receive server Finish. Use Des + MD5 deal data. {type:"finished"}
     *****************************************************************/
    JsonFree(json);
    len = read(user->fd, buffer, 2048);
    if (md5check(buffer, len))
    {
        json = TCPReceive(buffer, len - 16, user->desKey);
        jsonItem = JsonGet(json, "type");
        assert(jsonItem);
        assert(jsonItem->value->type == JSON_VALUE_TYPE_STRING);
        assert(!strcmp("finished", (char *)jsonItem->value->value));
        JsonFree(json);
        free(originalData);
        free(decodeData);
        printf("Connnecting step 8: Receive server Finish.\n");
        printf("Connnect established\n");
    }
    else
    {
        ERROR("Connecting step 8: MD5 check error!");
    }
    return fd;
}

void *TcpClientReadThread(void *fdPtr)
{
    int fd = *(int *)fdPtr;
    char buffer[2048];
    int len;
    JsonItem *jsonItem = NULL;
    Json *rec = NULL;
    while (1)
    {
        // 释放内存
        if (rec != NULL)
        {
            free(rec);
            rec = NULL;
        }
        len = read(fd, buffer, 2048);
        if (md5check(buffer, len))
        {
            printf("[Debug] MD5 check received data successfully!\n");
            rec = TCPReceive(buffer, len - 16, user->desKey);
            if (rec == NULL)
            {
                ERROR("TCPReceive return NULL");
                continue;
            }

            jsonItem = JsonGet(rec, "id");
            if (jsonItem == NULL || jsonItem->value->type != JSON_VALUE_TYPE_LONG)
            {
                ERROR("Receive chat data have not `id` member.");
                continue;
            }
            sprintf(buffer, "[* CHAT *][ID %lld]", *(int64 *)jsonItem->value->value);

            jsonItem = JsonGet(rec, "name");
            if (jsonItem == NULL || jsonItem->value->type != JSON_VALUE_TYPE_STRING)
            {
                ERROR("Receive chat data have not `name` member.");
                continue;
            }
            sprintf(buffer, "%s[%s]", buffer, (char *)jsonItem->value->value);

            jsonItem = JsonGet(rec, "content");
            if (jsonItem == NULL || jsonItem->value->type != JSON_VALUE_TYPE_STRING)
            {
                ERROR("Receive chat data have not `content` member.");
                continue;
            }
            sprintf(buffer, "%s %s", buffer, (char *)jsonItem->value->value);
            INFO(buffer);
            printf("%s\n", buffer);
        }
        else
        {
            sprintf(buffer, "Receive data's length is %d. MD5 check failed!", len);
            ERROR(buffer);
        }
    }
}

void *TcpClientWriteThread(void *fdPtr)
{
    int fd = *(int *)fdPtr;
    char buffer[2048];
    char buffer2[2048];
    char *strPtr;
    int len;
    Json *msgJson = JsonInit();
    Json *cmdJson = JsonInit();
    JsonPut(cmdJson, JsonStringItemInit("type", "change name"));
    JsonPut(msgJson, JsonStringItemInit("type", "chat data"));
    while (1)
    {
        fgets(buffer, 2048, stdin);
        buffer[2047] = 0;
        // cut \n
        buffer[strlen(buffer) - 1] = 0;
        // :c newName {type:"change name", content:"new name"}
        if (buffer[0] == ':' && buffer[1] == 'c' && buffer[2] == ' ')
        {
            // 截取
            buffer[19 + 3] = '\0';
            printf("[Debug] change name to `%s`.\n", buffer + 3);
            // 并不会有太大的并发异常，因为最后一个字符必定是\0
            strcpy(user->nickName, buffer + 3);
            JsonPut(cmdJson, JsonStringItemInit("content", buffer + 3));
            JsonString(cmdJson, buffer);
            printf("[Debug] %s", buffer);
            len = TCPSend(buffer, strlen(buffer), user->desKey, buffer2);
            if (write(fd, buffer2, len) != len)
            {
                sprintf(buffer2, "Send command change name data %s failed", buffer);
                INFO(buffer2);
                printf("[Debug] %s\n", buffer2);
            }
        }
        else
        {
            if (strlen(buffer) > 2000)
            {
                printf("[Client ERROR] The message %s is longer than 400 byte!\n", buffer);
                continue;
            }
            printf("[Debug] Send chat data`%s`.\n", buffer);
            JsonPut(msgJson, JsonStringItemInit("content", buffer));
            JsonPut(msgJson, JsonNumberInit("to", -1));
            JsonString(msgJson, buffer);
            printf("[Debug] send Data %s", buffer);
            len = TCPSend(buffer, strlen(buffer), user->desKey, buffer2);
            if (write(fd, buffer2, len) != len)
            {
                sprintf(buffer2, "Send chat data %s failed", buffer);
                INFO(buffer2);
                printf("[Debug] %s\n", buffer2);
            }
        }
    }
}

int TcpClientStart(int serverFd)
{
    pthread_t readTh, writeTh;
    // 单线程  debug
    // TcpClientWriteThread(&serverFd);
    // TcpClientReadThread(&serverFd);
    int *readFdPtr = (int *)malloc(sizeof(int));
    int *writeFdPtr = (int *)malloc(sizeof(int));
    *readFdPtr = serverFd;
    *writeFdPtr = serverFd;
    pthread_create(&readTh, NULL, TcpClientReadThread, readFdPtr);
    pthread_create(&writeTh, NULL, TcpClientWriteThread, writeFdPtr);
    pthread_join(readTh, NULL);
    printf("Read thread exit!\n");
    printf("Read thread exit!\n");
    pthread_join(writeTh, NULL);
    return 0;
}